/*
 * State.java
 *
 * Created on 7 de Julho de 2011, 17:18
 *
 */

package intopo.demonstrator.partnerselection;

import util.math.Rational;

import cern.jet.random.engine.MersenneTwister;

/**
 * Represents a state of the Markov Chain that models the partner
 * selection.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 7 de Julho de 2011
 */
final public class State
	extends intopo.partnerselection.AbstractProbabilityVector<State.ProbabilityCombination>
	implements Cloneable
{
	final Parameters parameters;
//	/**
//	 * Probability of the partner combination.
//	 */
//	final private int[] probability;
//	/**
//	 * Is the partner combination composed of only acceptable players or
//	 * not.
//	 */
//	final private boolean[] acceptable;
	
	static MersenneTwister supply = new MersenneTwister (new java.util.Date ());

	static private ProbabilityCombination[] createVector (int size)
	{
		ProbabilityCombination[] result;
		result = new ProbabilityCombination [size];
		while (size > 0) {
			size--;
			result [size] = new ProbabilityCombination (0);
		}
		return result;
	}
	static private ProbabilityCombination[] copyVector (State other)
	{
		ProbabilityCombination[] result;
		int size;
		size = other.size ();
		result = new ProbabilityCombination [size];
		while (size > 0) {
			size--;
			result [size] = new ProbabilityCombination (other.getSlot (size));
		}
		return result;
	}
	public State (Parameters parameters)
	{
		super (createVector (parameters.getPoolSize ()), parameters.getMaxProbability (), State.supply);
		this.parameters = parameters;
	}
	
	State (State state)
	{
		super (copyVector (state));
		this.parameters = state.parameters;
	}
	public State clone ()
	{
		return new State (this);
	}
	/**
	 * Returns {@code true} if the combination at the given slot is acceptable.
	 *
	 * <p> Combinations are charaterised as acceptable or not.
	 */
	public boolean isAcceptable (int combinationIndex)
	{
		return this.getSlot (combinationIndex).acceptable;
	}
	/**
	 * Returns {@code true} if this is an absorbing state. All
	 * non-acceptable partner combination have zero probability of being
	 * selected.
	 * @return {@code true} if this is an absorbing state.
	 */
	final public boolean isAbsorbing ()
	{
		int i;
		for (i = this.size () - 1; i >= 0; i--) {
			if (!this.isAcceptable (i) && getRawProbability (i) > 0) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Return the probability of selecting unacceptable combinations.  This
	 * method uses the probability vector.
	 */
	double probabilitySelectUnacceptableCombinations ()
	{
		double result = 0;
		for (int i = this.length1; i >= 0; i--) {
			if (!this.isAcceptable (i)) {
				result += getProbabilityAsDouble (i);
			}
		}
		return result;
	}
	/**
	 * Return the probability of selecting unacceptable combinations.  This
	 * method uses the probability vector.
	 */
	Rational probabilitySelectUnacceptableCombinationsAsRational ()
	{
		int sum = 0;
		for (int i = this.length1; i >= 0; i--) {
			if (!this.isAcceptable (i)) {
				sum += this.getRawProbability (i);
			}
		}
		return new Rational (sum, this.getMaxRawProbability ());
	}
	/**
	 * Returns the fraction of acceptable combinations.  The combination
	 * that was selected is unacceptable.
	 */
	double fractionAcceptableCombinations ()
	{
		int sum = 0;
		for (int i = this.length1; i >= 0; i--) {
			if (this.isAcceptable (i)) {
				sum++;
			}
		}
		return sum * 1.0 / this.size ();
	}
	/**
	 * Perform a transition of the Markov Chain.
	 */
	State transit (int selectedCombination)
	{
		if (!this.isAcceptable (selectedCombination)) {
			State result = new State (this);
			int remainderIdx = Math.abs (supply.nextInt ()) % this.parameters.getPoolSize ();
			int decrease = result.getRawProbability (selectedCombination)
				* result.parameters.getDeltaNumerator ()
				/ result.parameters.getDeltaDenominator ();
			result.distributeProbability (selectedCombination, decrease, remainderIdx);
			result.getSlot (selectedCombination).acceptable  = this.parameters.behaveAcceptably.eventSucess (supply);
			return result;
		}
		else {
			return this;
		}
	}
	/**
	 * Apply the given transition to this state and return the resulting
	 * state.  No pseudo-random number generator is used by this method.  If
	 * the given transition cannot be made {@code null} is returned.
	 */
	State transit (TransitionTo transition)
	{
		int selectedCombination, remainderIdx;
		switch (transition) {
		case S1:
			if (this.findCombinationIndex (true, 0, true) == -1) {
				return null;
			}
			else {
				return this;
			}
		case S2:
		case S3:
		case S4:
		case S5:
			selectedCombination = this.findCombinationIndex (false, 0, true);
			break;
		default:
			return null;
		}
		boolean remainderAcceptable =
			transition == TransitionTo.S3 || transition == TransitionTo.S5;
		remainderIdx = this.findCombinationIndex (remainderAcceptable, 0, false);
		if (remainderIdx == selectedCombination) {
			remainderIdx = this.findCombinationIndex (remainderAcceptable, remainderIdx + 1, false);
		}
		if (remainderIdx == -1) {
			return null;
		}
		State result = new State (this);
		int decrease = this.getRawProbability (selectedCombination) * this.parameters.getDeltaNumerator () / this.parameters.getDeltaDenominator ();
		result.distributeProbability (selectedCombination, decrease, remainderIdx);
		result.getSlot (selectedCombination).acceptable = 
			transition == TransitionTo.S4 || transition == TransitionTo.S5;
//		System.out.println (selectedCombination + " , " + remainderIdx);
		return result;
	}
	/**
	 * Return the index of a combination that matches the given criteria.
	 * The search begins at index {@code form}.  If parameter {@code
	 * positiveProbability} the returned combination must have a non-zero
	 * probability of being selected.
	 * @param acceptable indicates if the returned combination should be
	 * acceptable or not.
	 * @param from the index where the search begins
	 * @param positiveProbability if {@code true} the returned combination
	 * must have a non-zero probability of being selected.
	 */
	private int findCombinationIndex (boolean acceptable, int from, boolean positiveProbability)
	{
		int i = from;
		while (i < this.parameters.getPoolSize ()
			&& (this.isAcceptable (i) == !acceptable
				|| (positiveProbability && this.getRawProbability (i) == 0))) {
			i++;
		}
		if (i < this.parameters.getPoolSize ()) {
			return i;
		}
		else {
			return -1;
		}
	}
	/**
	 * Return the probability that this state of the Markov Chain can perform 
	 * one of the five possible transitions.  These five transitions
	 * are represented by enumeration {@code TransitionTo}.
	 */
	public double probabilityTransitTo (TransitionTo state)
	{
		double PU = this.probabilitySelectUnacceptableCombinations ();
		double PA = this.parameters.probabilityDrawAcceptableCombination ();
		double F = this.fractionAcceptableCombinations ();
		switch (state) {
		case S1:
			return 1 - PU;
		case S2:
			return PU * (1 - PA) * (1 - F);
		case S3:
			return PU * (1 - PA) * F;
		case S4:
			return PU * PA * (1 - F);
		case S5:
			return PU * PA * F;
		default:
			return -1;
		}
	}
	/**
	 * Enumeration that represents the five different transitions that the
	 * Markov chain can perform.
	 */
	public enum TransitionTo {
		/**
		 * The Markov Chain stays in the same state.
		 */
		S1,
		/**
		 * An unacceptable combination is selected and a unacceptable
		 * combination is drawn and the remainder goes to an unacceptable
		 * combination.
		 */
		S2,
		/**
		 * An unacceptable combination is selected and an unacceptable
		 * combination is drawn and the remainder goes to an acceptable
		 * combination.
		 */
		S3,
		/**
		 * An unacceptable combination is selected and an acceptable
		 * combination is drawn and the remainder goes to an unacceptable
		 * combination.
		 */
		S4,
		/**
		 * An unacceptable combination is selected and an acceptable
		 * combination is drawn and the remainder goes to an acceptable
		 * combination.
		 */
		S5;
	}
	static public class ProbabilityCombination
		extends intopo.partnerselection.AbstractProbabilityVector.Slot
		implements Cloneable
	{
		/**
		 * Is the partner combination composed of only acceptable players or
		 * not.
		 */
		private boolean acceptable;
		private ProbabilityCombination (int probabilityPartialSum)
		{
			this (probabilityPartialSum, false);
		}
		private ProbabilityCombination (int probabilityPartialSum, boolean acceptable)
		{
			super (probabilityPartialSum);
			this.acceptable = acceptable;
		}
		private ProbabilityCombination (ProbabilityCombination other)
		{
			super (other);
			this.acceptable = other.acceptable;
		}
		public ProbabilityCombination clone ()
		{
			return new ProbabilityCombination (this);
		}
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
